package com.supermap.fabricsdk;

import org.apache.commons.codec.binary.Hex;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.identity.X509Enrollment;
import org.hyperledger.fabric.sdk.security.CryptoPrimitives;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 工具类
 */
public class FabricsdkUtils {

    /**
     * 构造参数为字符串类型的交易提案请求
     * @param hfClient fabric 客户端
     * @param chaincodeName 链码名
     * @param functionName 方法名
     * @param args 参数
     * @return 交易提案请求
     */
    static TransactionProposalRequest getTransactionProposalRequest(HFClient hfClient, String chaincodeName, String functionName, String[] args) {
        TransactionProposalRequest transactionProposalRequest = hfClient.newTransactionProposalRequest();
        transactionProposalRequest.setFcn(functionName);
        transactionProposalRequest.setArgs(args);
        transactionProposalRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build());
        return transactionProposalRequest;
    }

    /**
     * 构造参数为字符串类型的交易提案请求(带私有数据)
     * @param hfClient fabric 客户端
     * @param chaincodeName 链码名
     * @param functionName 方法名
     * @param args 参数
     * @return 交易提案请求
     */
    static TransactionProposalRequest getTransactionProposalRequestWithPrivateData(HFClient hfClient, String chaincodeName, String functionName, String[] args) {
        TransactionProposalRequest transactionProposalRequest = hfClient.newTransactionProposalRequest();
        transactionProposalRequest.setFcn(functionName);
        transactionProposalRequest.setArgs(args);
        transactionProposalRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build());
        // 私有数据 transent 传递加密数据 （必须设置）
        Map<String, byte[]> transientMap = new HashMap<>();
//        transientMap.put("key", "yourprivatekey".getBytes(UTF_8));
        transientMap.put("key", "yourprivate".getBytes(UTF_8));
        try {
            // 这个是加在交易请求里面，并不会写到账本里面
            transactionProposalRequest.setTransientMap(transientMap);    // transent 传递加密数据  tmap.put("key", "yourprivatekey".getBytes());
        } catch (InvalidArgumentException e) {
            e.printStackTrace();
        }
        return transactionProposalRequest;
    }

    /**
     * 构造参数为字节数组类型的交易提案请求
     * @param hfClient fabric 客户端
     * @param chaincodeName 链码名
     * @param functionName 方法名
     * @param args 参数
     * @return 交易提案请求
     */
    static TransactionProposalRequest getTransactionProposalRequest(HFClient hfClient, String chaincodeName, String functionName, byte[][] args) {
        TransactionProposalRequest transactionProposalRequest = hfClient.newTransactionProposalRequest();
        transactionProposalRequest.setFcn(functionName);
        transactionProposalRequest.setArgBytes(args);
        transactionProposalRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build());
        return transactionProposalRequest;
    }

    /**
     * 构造参数为字节数组类型的交易提案请求
     * @param hfClient fabric 客户端
     * @param chaincodeName 链码名
     * @param functionName 方法名
     * @param args 参数
     * @return 交易提案请求
     */
    static TransactionProposalRequest getTransactionProposalRequestWithPrivateData(HFClient hfClient, String chaincodeName, String functionName, byte[][] args) {
        TransactionProposalRequest transactionProposalRequest = hfClient.newTransactionProposalRequest();
        transactionProposalRequest.setFcn(functionName);
        transactionProposalRequest.setArgBytes(args);
        transactionProposalRequest.setChaincodeID(ChaincodeID.newBuilder().setName(chaincodeName).build()); // 私有数据 transent 传递加密数据 （必须设置）
        Map<String, byte[]> transientMap = new HashMap<>();
//        transientMap.put("key", "yourprivatekey".getBytes(UTF_8));
        transientMap.put("key", "yourprivate".getBytes(UTF_8));
        try {
            // 这个是加在交易请求里面，并不会写到账本里面
            transactionProposalRequest.setTransientMap(transientMap);    // transent 传递加密数据  tmap.put("key", "yourprivatekey".getBytes());
        } catch (InvalidArgumentException e) {
            e.printStackTrace();
        }
        return transactionProposalRequest;
    }

    /**
     * 计算特定字节数组唯一的Hash值
     * @param bytes 原文
     * @return Hash字符串
     */
    public static String getSHA256(byte[] bytes) {
        if (null == bytes) {
            return null;
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.reset();
            messageDigest.update(bytes);
            return byte2Hex(messageDigest.digest());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算特定字符串唯一的Hash值
     * @param str 原文
     * @return Hash字符串
     */
    public static String getSHA256(String str) {
        return getSHA256(str.getBytes());
    }

    /**
     * 将字节数组编码为 Base64 字符串
     * @param bytes 原文
     * @return Base64 字符串
     */
    public static String encodeBase64(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    /**
     * Base64 解码
     * @param str Base64字符串
     * @return 原文
     */
    public static byte[] decodeBase64(String str) {
        return Base64.getDecoder().decode(str);
    }

    /**
     * 将 Base64 字符串解码为原文字节数组
     * @param bytes Base64字符串
     * @return 原文
     */
    public static byte[] decodeBase64(byte[] bytes) {
        return Base64.getDecoder().decode(bytes);
    }

    /**
     * 根据PEM字符串获取 X509 证书
     * @param pemStr PEM 字符串
     * @return X509 证书
     * @throws CertificateException
     */
    public static X509Certificate getCertFromPem(String pemStr) throws CertificateException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(pemStr.getBytes()));

    }

    /**
     * 将十六进制字节码转码为普通字符串。Fabric中的一些值以十六进制字节码方式返回，可用该方法进行转码
     * 如： \260-\256D\323i:\311\343\321\267\212f\206\230]a\0331\2440\264T\243qW*9\a\367\374q
     * 转码后为： b02dae44d3693ac9e3d1b78a6686985d611b31a430b454a371572a3907f7fc71
     * @param bytes 十六进制字节码
     * @return 字符串
     */
    public static String getHexString(byte[] bytes) {
        return Hex.encodeHexString(bytes);
    }

    /**
     * 字节数组转十六进制
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String byte2Hex(byte[] bytes) {
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i = 0; i < bytes.length; i++) {
            temp = Integer.toHexString(bytes[i] & 0xFF);
            if (temp.length() == 1) {
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        return stringBuffer.toString();
    }

    /**
     * 将区块链网络的配置文件转为 Map
     * @param networkFile
     * @return
     */
    public static Map<String, Object> getNetworkFileMap(File networkFile){
        Map<String, Object> networkFileMap = parseYamlFile(networkFile);
        return networkFileMap;
    }

    /**
     * 根据区块链的网络配置文件得到 链吗名
     * @param networkFileMap
     * @return
     */
    public static String getChainCodeName(Map<String, Object> networkFileMap){
        String channelName = getChannelName(networkFileMap);
        Map<String, Object> channelInfo = (Map<String, Object>) networkFileMap.get("channels");
        Map<String, Object> channelInfo1 = (Map<String, Object>) channelInfo.get(channelName);
        List<String> chaincodeNames = (List<String>) channelInfo1.get("chaincodes");
        String chaincodeName = chaincodeNames.get(0);
        return chaincodeName;
    }

    /**
     * 根据区块链的网络配置文件得到 通道名
     * @param networkFileMap
     * @return
     */
    public static String getChannelName(Map<String, Object> networkFileMap){
        Map<String, Object> channelInfo = (Map<String, Object>) networkFileMap.get("channels");
        Set<String> strings = channelInfo.keySet();
        String channelName = "";
        for(String str : strings){
            channelName = str;
        }
        return channelName;
    }

    public static String getHbaseIpddess(Map<String, Object> networkFileMap){
        Map<String, Object> hbaseInfo = (Map<String, Object>) networkFileMap.get("hbase");
        String hbaseIp = hbaseInfo.get("address").toString();
        return hbaseIp;
    }

    public static String getIpfsIpddess(Map<String, Object> networkFileMap){
        Map<String, Object> hbaseInfo = (Map<String, Object>) networkFileMap.get("ipfs");
        String hbaseIp = hbaseInfo.get("address").toString();
        return hbaseIp;
    }

    public static String getHbasePort(Map<String, Object> networkFileMap){
        Map<String, Object> hbaseInfo = (Map<String, Object>) networkFileMap.get("hbase");
        String port = hbaseInfo.get("port").toString();
        return port;
    }

    public static String getExternalStore(Map<String, Object> networkFileMap){
        String externalStore = networkFileMap.get("externalStore").toString();
        return externalStore;
    }

    /**
     * 解析 yaml 文件为 HashMap
     * @param yamlNetworkFile
     * @return
     */
    private static Map<String, Object> parseYamlFile(File yamlNetworkFile){
        InputStream input = null;
        try {
            input = new FileInputStream(yamlNetworkFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        Yaml yaml = new Yaml();
        Map<String, Object> networkFileMap = (Map<String, Object>) yaml.load(input);
        return networkFileMap;
    }

    /**
     * 构造 AdminUser
     * @param keyFile
     * @param certFile
     * @return
     * @throws Exception
     */
    public static User getUser(String userMspId, String userName, String keyFile, String certFile) throws Exception {

//        String userName = "peer0.orga.example.com";
//        String userMspId = "OrgA";
//        String keyFile = "D:\\crypto-config\\peerOrganizations\\orga.example.com\\users\\Admin@orga.example.com\\msp\\keystore\\3ba8424db358ec6083c852a5b3701217a4c8d201d60aaba2e42dd9a7867a0c62_sk";
//        String certFile = "D:\\crypto-config\\peerOrganizations\\orga.example.com\\users\\Admin@orga.example.com\\msp\\signcerts\\Admin@orga.example.com-cert.pem";
        Enrollment enrollment = loadFromPemFile(keyFile, certFile);
        //构造用户
        SmUser user = new SmUser();
        user.setName(userName);
        user.setEnrollment(enrollment);
        user.setMspId(userMspId);
        return user;
    }

    /**
     * 根据证书和秘钥构造 Enrollment 用于构建 User
     * @param keyFile
     * @param certFile
     * @return
     * @throws Exception
     */
    public static Enrollment loadFromPemFile(String keyFile, String certFile) throws Exception {
        byte[] keyPem = Files.readAllBytes(Paths.get(keyFile));     //载入私钥PEM文本
        byte[] certPem = Files.readAllBytes(Paths.get(certFile));   //载入证书PEM文本
        CryptoPrimitives suite = new CryptoPrimitives();            //载入密码学套件
        PrivateKey privateKey = suite.bytesToPrivateKey(keyPem);    //将PEM文本转换为私钥对象
        return new X509Enrollment(privateKey, new String(certPem));  //创建并返回X509Enrollment对象
    }
}
